using System;
using System.Collections.Generic;
using System.Text;

namespace FaceDetect
{
    using Emgu.CV;
    using Emgu.CV.Structure;
    using Emgu.CV.UI;
    using Emgu.CV.GPU;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Windows.Forms;

    public class FaceDetection
    {
        public static Bitmap FaceDetect(string file)
        {
            Bitmap bmp = new Bitmap(file);
            return FaceDetect(bmp);
        }

        public static Bitmap FaceDetect(Bitmap bmp)
        {
            Bitmap tmp = null;
            return FaceDetect(bmp, ref tmp);
        }

        public static Bitmap FaceDetect(Bitmap bmp, ref Bitmap res)
        {
            if (bmp == null) return null;
            Image<Bgr, Byte> image = new Image<Bgr, byte>(bmp); // read the files as an 8-bit Bgr image
            MCvAvgComp[] facesDetected = null;

            Stopwatch watch;
            string faceFileName = Application.StartupPath + @"\haarcascade_frontalface_alt.xml";
            string eyeFileName = Application.StartupPath + @"\haarcascade_eye.xml";

            if (GpuInvoke.HasCuda)
            {
                using (GpuCascadeClassifier face = new GpuCascadeClassifier(faceFileName))
                using (GpuCascadeClassifier eye = new GpuCascadeClassifier(eyeFileName))
                {
                    watch = Stopwatch.StartNew();

                    using (GpuImage<Bgr, Byte> gpuImage = new GpuImage<Bgr, byte>(image))
                    using (GpuImage<Gray, Byte> gpuGray = gpuImage.Convert<Gray, Byte>())
                    {
                        Rectangle[] faceRegion = face.DetectMultiScale(gpuGray, 1.1, 10, Size.Empty);
                        foreach (Rectangle f in faceRegion)
                        {
                            // Draw the face detected in the 0th (gray) channel with blue color
                            image.Draw(f, new Bgr(Color.Blue), 2);
                            using (GpuImage<Gray, Byte> faceImg = gpuGray.GetSubRect(f))
                            {
                                // For some reason a clone is required.
                                // Might be a bug of GpuCascadeClassifier in opencv
                                using (GpuImage<Gray, Byte> clone = faceImg.Clone())
                                {
                                    Rectangle[] eyeRegion = eye.DetectMultiScale(clone, 1.1, 10, Size.Empty);

                                    foreach (Rectangle e in eyeRegion)
                                    {
                                        Rectangle eyeRect = e;
                                        eyeRect.Offset(f.X, f.Y);
                                        image.Draw(eyeRect, new Bgr(Color.Red), 2);
                                    }
                                }
                            }
                        }
                    }
                    watch.Stop();
                }
            }
            else
            {
                //Read the HaarCascade objects
                using (HaarCascade face = new HaarCascade(faceFileName))
                using (HaarCascade eye = new HaarCascade(eyeFileName))
                {
                    watch = Stopwatch.StartNew();

                    using (Image<Gray, Byte> gray = image.Convert<Gray, Byte>()) // convert it to grayscale
                    {
                        // Normalizes brightness and increases contrast of the image
                        gray._EqualizeHist();

                        // Detect the faces  from the gray scale image and store the locations as rectangle
                        // The first dimensional is the channel
                        // The second dimension is the index of the rectangle in the specific channel
                        facesDetected = face.Detect(gray, 1.1, 10, Emgu.CV.CvEnum.HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new Size(20, 20));

                        foreach (MCvAvgComp f in facesDetected)
                        {
                            // Draw the face detected in the 0th (gray) channel with blue color
                            image.Draw(f.rect, new Bgr(Color.Blue), 2);

                            // Set the region of interest on the faces
                            gray.ROI = f.rect;
                            MCvAvgComp[] eyesDetected = eye.Detect(gray, 1.1, 10, Emgu.CV.CvEnum.HAAR_DETECTION_TYPE.DO_CANNY_PRUNING, new Size(20, 20));
                            gray.ROI = Rectangle.Empty;

                            foreach (MCvAvgComp e in eyesDetected)
                            {
                                Rectangle eyeRect = e.rect;
                                eyeRect.Offset(f.rect.X, f.rect.Y);
                                image.Draw(eyeRect, new Bgr(Color.Red), 2);
                            }
                        }
                    }
                    watch.Stop();
                }
            }

            // Display the image
            foreach (MCvAvgComp f in facesDetected) res = image.GetSubRect(f.rect).ToBitmap();

            return image.ToBitmap();
            //ImageViewer.Show(image, String.Format("Completed face and eye detection using {0} in {1} milliseconds", GpuInvoke.HasCuda ? "GPU" : "CPU", watch.ElapsedMilliseconds));
        }

        /// <summary>
        /// Check if both the managed and unmanaged code are compiled for the same architecture
        /// </summary>
        /// <returns>Returns true if both the managed and unmanaged code are compiled for the same architecture</returns>
        public static bool IsPlaformCompatable()
        {
            int clrBitness = Marshal.SizeOf(typeof(IntPtr)) * 8;
            if (clrBitness != CvInvoke.UnmanagedCodeBitness)
            {
                MessageBox.Show(String.Format("Platform mismatched: CLR is {0} bit, C++ code is {1} bit."
                   + " Please consider recompiling the executable with the same platform target as C++ code.",
                   clrBitness, CvInvoke.UnmanagedCodeBitness));
                return false;
            }
            return true;
        }

        #region Convert to grayscale
        // For information
        // http://www.switchonthecode.com/tutorials/csharp-tutorial-convert-a-color-image-to-grayscale
        public static Bitmap MakeGrayscaleSlow(Bitmap original)
        {
            // Make an empty bitmap the same size as original
            Bitmap newBitmap = new Bitmap(original.Width, original.Height);

            for (int i = 0; i < original.Width; i++)
            {
                for (int j = 0; j < original.Height; j++)
                {
                    // Get the pixel from the original image
                    Color originalColor = original.GetPixel(i, j);

                    // Create the grayscale version of the pixel
                    int grayScale = (int)((originalColor.R * .3) + (originalColor.G * .59) + (originalColor.B * .11));

                    // Create the color object
                    Color newColor = Color.FromArgb(grayScale, grayScale, grayScale);

                    // Set the new image's pixel to the grayscale version
                    newBitmap.SetPixel(i, j, newColor);
                }
            }

            return newBitmap;
        }

        public static Bitmap MakeGrayscaleMedium(Bitmap original)
        {
            unsafe
            {
                // Create an empty bitmap the same size as original
                Bitmap newBitmap = new Bitmap(original.Width, original.Height);

                // Lock the original bitmap in memory
                BitmapData originalData = original.LockBits(new Rectangle(0, 0, original.Width, original.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

                // Lock the new bitmap in memory
                BitmapData newData = newBitmap.LockBits(new Rectangle(0, 0, original.Width, original.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

                // Set the number of bytes per pixel
                int pixelSize = 3;

                for (int y = 0; y < original.Height; y++)
                {
                    // Get the data from the original image
                    byte* oRow = (byte*)originalData.Scan0 + (y * originalData.Stride);

                    // Get the data from the new image
                    byte* nRow = (byte*)newData.Scan0 + (y * newData.Stride);

                    for (int x = 0; x < original.Width; x++)
                    {
                        // Create the grayscale version
                        byte grayScale = (byte)((oRow[x * pixelSize] * .11) // B
                            + (oRow[x * pixelSize + 1] * .59) // G
                            + (oRow[x * pixelSize + 2] * .3)); // R

                        // Set the new image's pixel to the grayscale version
                        nRow[x * pixelSize] = grayScale; //B
                        nRow[x * pixelSize + 1] = grayScale; //G
                        nRow[x * pixelSize + 2] = grayScale; //R
                    }
                }

                // Unlock the bitmaps
                newBitmap.UnlockBits(newData);
                original.UnlockBits(originalData);

                return newBitmap;
            }
        }

        public static Bitmap MakeGrayscaleFast(Bitmap original)
        {
            // Create a blank bitmap the same size as original
            Bitmap newBitmap = new Bitmap(original.Width, original.Height);

            // Get a graphics object from the new image
            Graphics g = Graphics.FromImage(newBitmap);

            // Create the grayscale ColorMatrix
            ColorMatrix colorMatrix = new ColorMatrix(new float[][] { 
                new float[] { .3f, .3f, .3f, 0, 0 }, 
                new float[] { .59f, .59f, .59f, 0, 0 }, 
                new float[] { .11f, .11f, .11f, 0, 0 }, 
                new float[] { 0, 0, 0, 1, 0 }, 
                new float[] { 0, 0, 0, 0, 1 } });

            // Create some image attributes
            ImageAttributes attributes = new ImageAttributes();

            // Set the color matrix attribute
            attributes.SetColorMatrix(colorMatrix);

            // Draw the original image on the new image
            // Using the grayscale color matrix
            g.DrawImage(original, new Rectangle(0, 0, original.Width, original.Height),
                0, 0, original.Width, original.Height, GraphicsUnit.Pixel, attributes);

            // Dispose the Graphics object
            g.Dispose();
            return newBitmap;
        }
        #endregion
    }
}